<?php
/**
 * Description of Validator
 *
 * @author joona
 */
abstract class Core_Validator
{
    /**
     * Collection of validation rules
     * Format : 'nameOfVariable' => array('type' => ...)
     * Supported rules are:
     *      type, allowNull, minLength, maxLength, minValue, maxValue, regexp
     * @var Array
     */
    static $validationRules = array();
    static $validationErrors = array();

    /**
     * Set validation/format rules to a variable
     *
     * Supported rules are:
     *      type, allowNull, minLength, maxLength, minValue, maxValue, regexp
     * For example:
     * $db->setRule('userID', array(
     *      'type' => 'integer', //integer, double, string, bool
     *      'minValue' => 1
     * ));
     *
     * @param STRING $varName Name of the variable rules apply to
     * @param ARRAY $rule Array of validation/format rules
     */
    public static function setRule($varName, array $rule) {
        self::$validationRules[$varName] = $rule;
    }

    /**
     * Remove all rules from database handler
     */
    public static function resetRules() {
        self::$validationRules = array();
    }

    /**
     * Validate a variable agains pre-set rules
     * @param string $varName
     * @param misc $value
     * @return bool True if success, false if not
     */
    public static function validate($varName, $value)
    {
        if(!isset(self::$validationRules[$varName]))
            return self::addError($varName, 'No validation rule set');
        else
            return self::validateAgainst(
                    $value,
                    self::$validationRules[$varName],
                    $varName
                );
    }

    public function validateVariables($listOfVariables)
    {
        $success = 1;
        foreach($listOfVariables as $varName => $value)
        {
            $success *= self::validate($varName, $value);
        }
        return $success;
    }
    
    /**
     * Checks a value agains given rules
     * @param MISC $value Misc value to be checked
     * @param Array $rule Array of conditions that must be met
     * @param string $varName Name of the variable (for logging)
     * @return Bool
     */
    public static function validateAgainst($value, array $rule, $varName = null) {
        if (is_null($value))
            return self::checkNullValue($value, $rule, $varName);
        if (!self::checkType($value, $rule, $varName))
            return false;
        if (!self::checkRegularExpression($value, $rule, $varName))
            return false;
        if (self::isNumeric($value) &&
                !self::checkNumericValue($value, $rule, $varName))
            return false;
        else if (!self::checkStringValue($value, $rule, $varName))
            return false;

        return true;
    }


    /**
     * Adds an error to list, always returns false
     * @param STRING $varName Name of the variable error relates to
     * @param STRING $message Message describing error
     */
    private static function addError($varName, $message)
    {
        self::$validationErrors[] = 
            is_null($varName) 
                ? "$message"
                : "Invalid '$varName': $message";
        return false;
    }

    /**
     * Get possible validation errors
     * @return array
     */
    public static function getErrors()
    {
        return self::$validationErrors;
    }

    public static function resetErrors()
    {
        self::$validationErrors = array();
    }

    

    /**
     * Checks if the given value is either float or integer number
     * @param $value Misc
     * @return Bool
     */
    private static function isNumeric($value) {
        return gettype($value) == 'double' || gettype($value) == 'integer';
    }

    /**
     * Checks that the type of given value matches the rule['type'] demand
     * If no 'type' condition is presented, type of value is accepted
     *
     * @param $value Misc value to be checked
     * @param $rule Array of conditions (with 'type' => type)
     * @return Bool
     */
    private static function checkType($value, $rule, $varName = null)
    {
        return (isset($rule['type']) && gettype($value) != $rule['type'])
            ? self::addError($varName,
                    'Wrong type - expected '.
                    $rule['type'].' '.gettype($value).' given')
            : true;
    }

    /**
     * Checks if given value matches regexp in given rules
     * @param Misc $value Value to check
     * @param Array $rule Containging 'regexp' key with regular expression
     * @return Bool True if no regexp rule is present or it is a match, otherwise False
     */
    private static function checkRegularExpression($value, $rule, $varName = null)
    {
        if (isset($rule['regexp']) && !preg_match($rule['regexp'], $value))
            return self::addError($varName,
                    'Regexp failed :'.$rule['regexp']);
        return true;
    }

    /**
     * Checks if possible NULL value is allowed
     *
     * If no 'allowNull' condition is presented or it is set to false, null $value
     * will cause FALSE result
     *
     * @param $value Misc value to be checked
     * @param $rule Array of conditions (with 'allowNull')
     * @return Bool
     */
    private static function checkNullValue($value, $rule, $varName = null)
    {
        if (is_null($value)) {
            if (!isset($rule['allowNull']))
                return self::addError($varName, 'Rules don\'t allow NULL');
            else if ($rule['allowNull'] === false)
                return self::addError($varName, 'Rules forbit NULL');
        }
        return true;
    }

    /**
     * Checks min and max conditions of a numeric value
     * @param $value INT/FLOAT value to be checked
     * @param $rule Array of conditions (with 'maxValue' and/or 'minValue')
     * @return <type>
     */
    private static function checkNumericValue($value, $rule, $varName = null)
    {
        if (isset($rule['maxValue']) && $value > $rule['maxValue'])
            return self::addError($varName, 
                    'Value is bigger than maximum allowed: '.$rule['maxValue']);
        if (isset($rule['minValue']) && $value < $rule['minValue'])
            return self::addError($varName,
                    'Value is smaller than minimum allowed: '.$rule['minValue']);
        return true;
    }

    /**
     * Checks that string variable meets validation rules
     * @param $value String value to be checked
     * @param $rule Array of conditions (with 'maxLength' and/or 'minLength')
     * @return Bool
     */
    private static function checkStringValue($value, $rule, $varName = null)
    {
        if (isset($rule['maxLength']) && strlen($value) > $rule['maxLength'])
            return self::addError($varName,
                    'Too long ('.strlen($value).') max '.$rule['maxLength'].' allowed');
        if (isset($rule['minLength']) && strlen($value) < $rule['minLength'])
            return self::addError($varName,
                    'Too short ('.strlen($value).') min '.$rule['minLength'].' required');
        return true;
    }
}

class Core_Validator_Exception extends Exception {}
